package xyz.scootaloo.mono.security

import org.apache.shiro.SecurityUtils
import org.apache.shiro.mgt.DefaultSubjectDAO
import org.apache.shiro.mgt.SecurityManager
import org.apache.shiro.mgt.SessionStorageEvaluator
import org.apache.shiro.mgt.SubjectDAO
import org.apache.shiro.spring.LifecycleBeanPostProcessor
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor
import org.apache.shiro.spring.web.ShiroFilterFactoryBean
import org.apache.shiro.web.mgt.DefaultWebSecurityManager
import org.apache.shiro.web.mgt.DefaultWebSessionStorageEvaluator
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator
import org.springframework.boot.web.servlet.FilterRegistrationBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.web.filter.DelegatingFilterProxy
import xyz.scootaloo.mono.data.service.AuthService
import xyz.scootaloo.mono.security.filter.AnyRolesAuthorizationFilter
import xyz.scootaloo.mono.security.filter.BonJwtFilter
import xyz.scootaloo.mono.security.realm.JwtRealm
import xyz.scootaloo.mono.security.realm.PasswordRealm
import xyz.scootaloo.mono.security.service.AccountService
import xyz.scootaloo.mono.security.util.KW_SHIRO
import javax.servlet.DispatcherType
import javax.servlet.Filter


/**
 * 此模块用于保护web应用的安全, 并做为starter可以被其他springboot模块引入,
 * 当其他模块引入此模块后, 将自动获得以下功能
 * - 对 web 路径进行保护, 可以指定访问每个路径所需要的权限, todo 使用DSL进行配置
 * - 提供用户登陆的接口, 无需编写额外的登陆实现, 访问 /account/login 即可完成登陆
 * - 提供用户权限相关的增删改查接口, todo
 * - web 应用返回给前端的每条 response 都将自动加入跨域设置
 *
 * ### 资料参考
 * [shiro过滤器](https://www.cnblogs.com/haizhilangzi/p/15016642.html)
 * [shiro整合jwt](https://www.jianshu.com/p/0b1131be7ace)
 * [shiro-starter](https://segmentfault.com/a/1190000014479154)
 * [shiro身份认证](https://blog.csdn.net/qq_28082757/article/details/100776165)
 *
 * > web安全实现为shiro
 *
 * @author flutterdash@qq.com
 * @since 2021/7/25 22:20
 */
@Configuration
@ComponentScan(basePackages = ["xyz.scootaloo.mono.security"])
class SecurityAutoConfiguration {

    @Bean
    fun securityManager(
        jwtRealm: JwtRealm, passwordRealm: PasswordRealm
    ): SecurityManager {
        return DefaultWebSecurityManager().apply {
            subjectDAO = subjectDao()
            realms = listOf(jwtRealm, passwordRealm)
            SecurityUtils.setSecurityManager(this)
        }
    }

    @Bean
    fun delegatingFilterProxy(
        securityManager: SecurityManager,
        authService: AuthService,
        accountService: AccountService
    ): FilterRegistrationBean<Filter> {
        return FilterRegistrationBean<Filter>().apply {
            val proxy = DelegatingFilterProxy().apply {
                setTargetFilterLifecycle(true)
                setTargetBeanName(KW_SHIRO.filter)
            }
            setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC)
            filter = proxy
            isAsyncSupported = true
            isEnabled = true
        }
    }

    @Bean(KW_SHIRO.filter)
    fun shiroFilterFactoryBean(
        securityManager: SecurityManager,
        authService: AuthService,
        accountService: AccountService
    ): ShiroFilterFactoryBean {
        return ShiroFilterFactoryBean().apply {
            setSecurityManager(securityManager)
            filterChainDefinitionMap = createFilterChainDefinitionMap()
            filters = mapOf(
                "authc" to BonJwtFilter(accountService),
                "roles" to AnyRolesAuthorizationFilter()
            )
        }
    }

    @Bean
    fun getDefaultAdvisorAutoProxyCreator(): DefaultAdvisorAutoProxyCreator {
        /**
         * setUsePrefix(false)用于解决一个奇怪的bug。在引入spring aop的情况下。
         * 在@Controller注解的类的方法中加入@RequiresRole注解，会导致该方法无法映射请求，导致返回404。
         * 加入这项配置能解决这个bug
         */
        return DefaultAdvisorAutoProxyCreator().apply {
            isUsePrefix = true
        }
    }

    @Bean
    fun authorizationAttributeSourceAdvisor(
        securityManager: SecurityManager
    ): AuthorizationAttributeSourceAdvisor {
        /**
         * 开启shiro aop注解支持.否则@RequiresRoles等注解无法生效
         * 使用代理方式;
         */
        return AuthorizationAttributeSourceAdvisor().apply {
            setSecurityManager(securityManager)
        }
    }

    @Bean
    fun lifecycleBeanPostProcessor(): LifecycleBeanPostProcessor {
        return LifecycleBeanPostProcessor()
    }

    private fun subjectDao(): SubjectDAO {
        return DefaultSubjectDAO().apply {
            sessionStorageEvaluator = sessionStorageEvaluator()
        }
    }

    private fun sessionStorageEvaluator(): SessionStorageEvaluator {
        return DefaultWebSessionStorageEvaluator().apply {
            isSessionStorageEnabled = false
        }
    }

    fun createFilterChainDefinitionMap(): Map<String, String> {
        return linkedMapOf(
            // 不需要登陆的路径
            "/account/**" to "anon",
            "/common/**" to "anon",
            "/test/**" to "anon",

            // 其余为需要登陆后才能访问的路径
            "/**" to "authc"
        )
    }

}
